//
// Copyright Funix 2012
// http://www.cppx.net/ 
// funix@cppx.net
// Distributed under the Boost Software License, Version 1.0.
// (See copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include <list>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/function.hpp>

namespace cppx {

    /// \brief Template resource pool for any resource like file, database connection, etc.
    /// It's convenient and efficient to manage resource. It's base on boost.shared_ptr's custom
    /// deleter. Call ResourcePool.get to get resource, and the resource will be automatically
    /// recycled when the resource pointer no longer referenced.
    template<typename R>
    struct ResourcePool : boost::noncopyable {
        typedef R ValueType;
        typedef boost::shared_ptr<R> ValuePtr;        /// pointer to resource
        typedef boost::function<ValuePtr(void)> Allocator;

        /// \return count of allocated resources
        size_t size() const {
            return _proxy->size();
        }
        /// \return count of allocated but unused resources
        size_t unused() const {
            return _proxy->unused();
        }
        /// deallocate unused resources
        void shrink() {
            _proxy->shrink();
        }

        /// get resource from pool, or alloc one if no unused in pool
        /// \return resource pointer. null pointer returned only when alloc failed.
        ValuePtr get() {
            ValuePtr res = _proxy->get();
            if(res)
                return ValuePtr(res.get(), Deleter(_proxy, res));
            else
                return res;
        }

        explicit ResourcePool(const Allocator & allocator) :
        _proxy(new Proxy(allocator)) {
        }
    private:
        struct Proxy {

            size_t size() const {
                return _size;
            }
            size_t unused() const {
                return _unused;
            }

            void recycle(const ValuePtr & res) {
                boost::mutex::scoped_lock lock(_mutex);
                _pool.push_back(res);
                _unused ++;
            }

            void shrink() {
                PoolType tmp;
                {
                    boost::mutex::scoped_lock lock(_mutex);
                    tmp.swap(_pool);
                    _size -= _unused;
                    _unused = 0;
                }
            }

            ValuePtr get() {
                //try to get from pool
                {
                    boost::mutex::scoped_lock lock(_mutex);
                    if(!_pool.empty()) {
                        ValuePtr res = _pool.front();
                        _pool.pop_front();
                        -- _unused;
                        return res;
                    }
                }

                //no unused in pool
                // alloc one
                ValuePtr res = _allocator();
                if(res) {
                    boost::mutex::scoped_lock lock(_mutex);
                    //one alloced, increase count
                    ++ _size;
                }
                return res;
            }

            Proxy(const Allocator & allocator):
            _allocator(allocator), _size(0), _unused(0) {
            }
        protected:
            const Allocator _allocator;

            typedef std::list<ValuePtr> PoolType;
            PoolType _pool;
            boost::mutex _mutex;

            size_t _size;
            size_t _unused;
        };

        typedef boost::weak_ptr<Proxy> ProxyWeakPtr;
        typedef boost::shared_ptr<Proxy> ProxyPtr;

        /// \brief A custom deleter of resource pointer to recycle resource
        struct Deleter {        
            Deleter(const ProxyWeakPtr & proxy, const ValuePtr & res):
        _proxy(proxy), _res(res)
        {}
        void operator()(void *) {
            /// try lock proxy
            ProxyPtr proxy = _proxy.lock();
            if(proxy)
                proxy->recycle(_res);
            ///else, the resource pool gone. nothing to do, and _res will do the right thing.
        }
        private:
            /// proxy weak pointer
            ProxyWeakPtr _proxy;
            /// the real resource
            ValuePtr _res;
        };

        ProxyPtr _proxy;
    };

}
